package com.canal.display.filter;

import com.canal.display.entity.TokenBasedAuthentication;
import com.canal.display.handler.TokenFailureHandler;
import com.canal.display.util.TextValidator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Administrator
 * @description
 * @date 2019年03月17 13:25
 */
public class TokenAuthenticationFilter extends OncePerRequestFilter  {

    private static final String accessTokenName = "Access-Token";

    /**
     * 需要认证的请求匹配器
     */
    private RequestMatcher requiresAuthenticationRequestMatcher;

    /**
     * 允许通过的请求列表
     */
    private List<RequestMatcher> permissiveRequestMatchers;

    /**
     * 认证管理器
     */
    private AuthenticationManager authenticationManager;

    private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    private AuthenticationFailureHandler failureHandler = new TokenFailureHandler();

    public TokenAuthenticationFilter() {
        this.requiresAuthenticationRequestMatcher = new RequestHeaderRequestMatcher(accessTokenName);
    }

    /**
     * 获取头部的token
     * @param request
     * @return
     */
    protected String getToken(HttpServletRequest request) {
        return request.getHeader(accessTokenName);

    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        //判断请求是否需要认证,就是头部是否携带token
        if(!requiresAuthentication(request,response)) {
            //查看允许的请求列表中是否包含该请求
            if(permissiveRequest(request)) {
                filterChain.doFilter(request,response);
                return;
            }
        }
        Authentication authResult = null;
        AuthenticationException failed = null;
        try {
            String token = getToken(request);
            if(StringUtils.isNoneBlank(token)) {
                //对token进行正则校验 token为uuid
                if(!TextValidator.isUuid(token)) {
                    failed = new InsufficientAuthenticationException("token is invalid");
                }
                TokenBasedAuthentication authentication = new TokenBasedAuthentication(token);
                authResult = getAuthenticationManager().authenticate(authentication);
            } else {
                failed = new InsufficientAuthenticationException("token is empty");
            }
        } catch (InternalAuthenticationServiceException e) {
            failed = e;
        } catch (AuthenticationException e) {
            failed = e;
        }
        if(authResult != null) {
            successfulAuthentication(request,response,filterChain,authResult);
        } else if(!permissiveRequest(request)) {
            unsuccessfulAuthentication(request,response,failed);
            return;
        }
        filterChain.doFilter(request,response);
    }

    @Override
    public void afterPropertiesSet() {
        Assert.notNull(authenticationManager, "authenticationManager must be specified");
        Assert.notNull(successHandler, "successHandler must be specified");
        Assert.notNull(failureHandler, "failureHandler must be specified");
    }

    /**
     * 错误处理
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    protected void unsuccessfulAuthentication(HttpServletRequest request,
                                              HttpServletResponse response, AuthenticationException failed)
            throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        failureHandler.onAuthenticationFailure(request, response, failed);
    }

    /**
     * 成功处理
     * @param request
     * @param response
     * @param chain
     * @param authResult
     * @throws IOException
     * @throws ServletException
     */
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException{
        SecurityContextHolder.getContext().setAuthentication(authResult);
        successHandler.onAuthenticationSuccess(request, response, authResult);
    }

    protected AuthenticationManager getAuthenticationManager() {
        return authenticationManager;
    }

    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    /**
     * 判断请求是否需要认证
     * @param request
     * @param response
     * @return
     */
    protected boolean requiresAuthentication(HttpServletRequest request,
                                             HttpServletResponse response) {
        return requiresAuthenticationRequestMatcher.matches(request);
    }

    /**
     * 是否为允许请求（不经过过滤器）
     * @param request
     * @return
     */
    protected boolean permissiveRequest(HttpServletRequest request) {
        if(permissiveRequestMatchers == null) {
            return false;
        }
        for(RequestMatcher permissiveMatcher : permissiveRequestMatchers) {
            if(permissiveMatcher.matches(request)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 设置允许通过过滤器的请求
     * @param urls
     */
    public void setPermissiveUrl(String... urls) {
        if(permissiveRequestMatchers == null) {
            permissiveRequestMatchers = new ArrayList<>();
        }
        for(String url : urls) {
            permissiveRequestMatchers.add(new AntPathRequestMatcher(url));
        }
    }

    /**
     * 认证通过后处理
     * @param successHandler
     */
    public void setAuthenticationSuccessHandler(
            AuthenticationSuccessHandler successHandler) {
        Assert.notNull(successHandler, "successHandler cannot be null");
        this.successHandler = successHandler;
    }

    /**
     * 认证失败后处理
     * @param failureHandler
     */
    public void setAuthenticationFailureHandler(
            AuthenticationFailureHandler failureHandler) {
        Assert.notNull(failureHandler, "failureHandler cannot be null");
        this.failureHandler = failureHandler;
    }

    protected AuthenticationSuccessHandler getSuccessHandler() {
        return successHandler;
    }

    protected AuthenticationFailureHandler getFailureHandler() {
        return failureHandler;
    }
}
